package com.piece.mall.service;

import com.piece.core.framework.support.convert.Convert;
import com.piece.core.framework.support.dto.PayloadDTO;
import com.piece.core.framework.util.collection.CollectionUtil;
import com.piece.core.framework.util.math.SequenceUtil;
import com.piece.core.framework.util.message.MessageFactory;
import com.piece.core.framework.util.object.BeanUtil;
import com.piece.core.framework.util.string.JsonUtil;
import com.piece.core.framework.util.string.StringUtil;
import com.piece.core.log.client.dto.UserDTO;
import com.piece.core.log.config.UserContextHolder;
import com.piece.core.log.enums.MessageLogType;
import com.piece.core.mongo.entity.MongoMessageLog;
import com.piece.mall.api.constant.MallConstant;
import com.piece.mall.api.constant.MallRegisterConstants;
import com.piece.mall.model.PmsSkuInfo;
import com.piece.mall.model.SmsSeckillSession;
import com.piece.mall.model.SmsSeckillSkuRelation;
import com.piece.mall.vo.SeckillOrderVo;
import com.piece.mall.vo.SeckillSkuVo;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
@Transactional
public class SeckillService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Resource
    private MessageFactory messageFactory;

    @Resource
    private SmsSeckillSessionService smsSeckillSessionService;

    @Lazy
    @Resource
    private PmsSkuInfoService pmsSkuInfoService;

    /**
     * 上架需要秒杀的商品
     */
    public void uploadSeckillSkuLatest3Days() {
        this.uploadSeckillSkuLatest(3);
    }

    /**
     * 上架需要秒杀的商品
     * @param day 最近{day}天内上架的商品
     */
    public void uploadSeckillSkuLatest(int day) {
        // 获取场次
        List<SmsSeckillSession> sessions = smsSeckillSessionService.getSessionWithinDay(day);
        // 上架场次信息
        saveSessionInfos(sessions);
        // 上架商品信息
        saveSessionSkuInfo(sessions);
    }

    /**
     * 上架场次
     */
    private void saveSessionInfos(List<SmsSeckillSession> sessions) {
        if (CollectionUtil.isNotEmpty(sessions)) {
            sessions.stream().forEach(session -> {
                // 遍历场次
                long startTime = session.getStartTime().getTime();
                long endTime = session.getEndTime().getTime();
                String key = MallConstant.SESSION_CACHE_PREFIX + startTime + "_" + endTime;
                // 判断场次是否已上架（幂等性）
                Boolean hasKey = redisTemplate.hasKey(key);
                if (!hasKey) {
                    // 封装场次信息
                    List<String> skuIds = session.getRelationSkus().stream()
                            .map(item -> item.getSessionId() + "_" + item.getSkuId().toString())
                            .collect(Collectors.toList());
                    // 上架
                    redisTemplate.opsForList().leftPushAll(key, skuIds);
                }
            });
        }
    }

    /**
     * 上架商品信息
     */
    private void saveSessionSkuInfo(List<SmsSeckillSession> sessions) {
        if (CollectionUtil.isNotEmpty(sessions)) {
            // 查询所有商品信息
            List<Long> skuIds = new ArrayList<>();
            sessions.stream().forEach(session -> {
                List<Long> ids = session.getRelationSkus().stream().map(SmsSeckillSkuRelation::getSkuId).collect(Collectors.toList());
                skuIds.addAll(ids);
            });
            List<PmsSkuInfo> skuInfos = pmsSkuInfoService.findByIds(skuIds);
            // 封装商品信息
            Map<Long, PmsSkuInfo> skuMap = skuInfos.stream().collect(Collectors.toMap(PmsSkuInfo::getSkuId, val -> val));
            // 绑定秒杀商品hash
            BoundHashOperations<String, Object, Object> operations = redisTemplate.boundHashOps(MallConstant.SECKILL_CACHE_KEY);
            // 遍历场次
            sessions.stream().forEach(session -> {
                // 遍历商品
                session.getRelationSkus().stream().forEach(relation -> {
                    // 判断商品是否已上架（幂等性）
                    String skuKey = relation.getSessionId().toString() + "_" + relation.getSkuId().toString();
                    if (!operations.hasKey(skuKey)) {
                        SeckillSkuVo seckillSku = new SeckillSkuVo();
                        // 封装商品信息
                        PmsSkuInfo skuInfo = skuMap.get(relation.getSkuId());
                        BeanUtil.copyProperties(skuInfo, seckillSku, true);
                        seckillSku.setSkuInfo(skuInfo);
                        // 秒杀开始时间
                        seckillSku.setStartTime(session.getStartTime().getTime());
                        // 秒杀结束时间
                        seckillSku.setEndTime(session.getEndTime().getTime());
                        // 商品随机码：用户参与秒杀时，请求需要带上随机码（防止恶意攻击）
                        String randomCode = SequenceUtil.uuid();
                        seckillSku.setRandomCode(randomCode);
                        // 上架商品
                        operations.put(skuKey, JsonUtil.toJson(seckillSku));
                        // 上架商品的分布式信号量，key：商品随机码 值：库存（限流）
                        RSemaphore semaphore = redissonClient.getSemaphore(MallConstant.SKU_STOCK_SEMAPHORE + randomCode);
                        // 信号量（扣减成功才进行后续操作，否则快速返回）
                        semaphore.trySetPermits(seckillSku.getSeckillCount());
                    }
                });
            });
        }
    }

    /**
     * 获取当前可以参加秒杀商品的信息
     */
    public List<SeckillSkuVo> getCurrentSeckillSkus() {
        // 当前时间
        long currentTime = System.currentTimeMillis();
        // 查询所有秒杀场次key
        Set<String> keys = redisTemplate.keys(MallConstant.SESSION_CACHE_PREFIX + "*");
        for (String key : keys) {
            // 截取时间
            String replace = key.replace(MallConstant.SESSION_CACHE_PREFIX, "");
            String[] time = replace.split("_");
            long startTime = Long.parseLong(time[0]);
            long endTime = Long.parseLong(time[1]);
            // 判断是否属于该秒杀场次
            if (currentTime >= startTime && currentTime <= endTime) {
                // 查询当前场次信息范围内100条数据
                List<String> sessionIdSkuIds = redisTemplate.opsForList().range(key, -100, 100);
                // 获取商品信息
                BoundHashOperations<String, String, String> operations = redisTemplate.boundHashOps(MallConstant.SECKILL_CACHE_KEY);
                assert sessionIdSkuIds != null;
                // 批量获取商品信息(sessionId_skuId)
                List<String> skus = operations.multiGet(sessionIdSkuIds);
                if (CollectionUtil.isNotEmpty(skus)) {
                    // 将商品信息反序列成对象
                    List<SeckillSkuVo> skuInfos = skus.stream().map(sku -> {
                        SeckillSkuVo skuInfo = JsonUtil.toBean(sku, SeckillSkuVo.class);
                        return skuInfo;
                    }).collect(Collectors.toList());
                    return skuInfos;
                }
                break;
            }
        }

        return null;
    }

    /**
     * 查询商品当前时间秒杀信息
     */
    public SeckillSkuVo getSkuSeckilInfo(Long skuId) {
        // 获取所有商品的key
        BoundHashOperations<String, String, String> operations = redisTemplate.boundHashOps(MallConstant.SECKILL_CACHE_KEY);
        Set<String> keys = operations.keys();
        if (CollectionUtil.isNotEmpty(keys)) {
            String lastIndex = "_" + skuId;
            for (String key : keys) {
                if (key.lastIndexOf(lastIndex) > -1) {
                    // 商品匹配成功
                    String jsonString = operations.get(key);
                    SeckillSkuVo skuInfo = JsonUtil.toBean(jsonString, SeckillSkuVo.class);
                    Long currentTime = System.currentTimeMillis();
                    Long endTime = skuInfo.getEndTime();
                    if (currentTime <= endTime) {
                        // 当前时间小于截止时间
                        Long startTime = skuInfo.getStartTime();
                        if (currentTime >= startTime) {
                            // 返回当前正处于秒杀的商品信息
                            return skuInfo;
                        }
                        // 返回预告信息，不返回随机码
                        skuInfo.setRandomCode(null);
                        return skuInfo;
                    }
                }
            }
        }

        return null;
    }

    /**
     * 秒杀商品
     *
     * @param killId  sessionId_skuid
     * @param key     随机码
     * @param num     商品件数
     */
    public String kill(String killId, String key, Integer num) throws InterruptedException {
        long start = System.currentTimeMillis();
        // 获取当前登录用户信息
        UserDTO user = UserContextHolder.get();
        // 获取当前秒杀商品的详细信息
        BoundHashOperations<String, String, String> operations = redisTemplate.boundHashOps(MallConstant.SECKILL_CACHE_KEY);
        String jsonString = operations.get(killId);

        if (StringUtil.isEmpty(jsonString)) {
            return null;
        }

        // 获取商品信息
        SeckillSkuVo skuInfo = JsonUtil.toBean(jsonString, SeckillSkuVo.class);
        long currentTime = System.currentTimeMillis();
        Long startTime = skuInfo.getStartTime();
        Long endTime = skuInfo.getEndTime();
        if (currentTime >= startTime && currentTime <= endTime) {
            String randomCode = skuInfo.getRandomCode();
            if (randomCode.equals(key)) {
                // 获取每人限购数量
                Integer seckillLimit = skuInfo.getSeckillLimit();
                // 获取信号量
                String seckillCount = Convert.toStr(redisTemplate.opsForValue().get(MallConstant.SKU_STOCK_SEMAPHORE + randomCode), "0");
                Integer count = Integer.valueOf(seckillCount);
                if (num > 0 && num <= seckillLimit && count > num) {
                    // 校验是否重复秒杀
                    String userKey = MallConstant.SECKILL_USER_PREFIX + user.getId() + "_" + killId;
                    // 自动过期时间(活动结束时间 - 当前时间)
                    Long ttl = endTime - currentTime;
                    Boolean isRepeat = redisTemplate.opsForValue().setIfAbsent(userKey, num.toString(), ttl, TimeUnit.MILLISECONDS);
                    if (isRepeat) {
                        // 占位成功,扣减信号量（防止超卖）
                        RSemaphore semaphore = redissonClient.getSemaphore(MallConstant.SKU_STOCK_SEMAPHORE + randomCode);
                        boolean isAcquire = semaphore.tryAcquire(num, 100, TimeUnit.MILLISECONDS);
                        if (isAcquire) {
                            String orderSn = SequenceUtil.next() + "";
                            SeckillOrderVo order = new SeckillOrderVo();
                            order.setOrderSn(orderSn);
                            order.setPromotionSessionId(skuInfo.getPromotionSessionId());
                            order.setSkuId(skuInfo.getSkuId());
                            order.setSeckillPrice(skuInfo.getSeckillPrice());
                            order.setNum(num);
                            order.setUserId(user.getId());
                            order.setUserName(user.getName());

                            try {
                                // 发送秒杀订单
                                MongoMessageLog messageLog = new MongoMessageLog();
                                messageLog.setTitle("创建秒杀订单通知");
                                messageLog.setEntity(JsonUtil.toJson(order));
                                messageLog.setType(MessageLogType.MESSAGE.getType() + "");
                                messageLog.setExchange(MallRegisterConstants.ORDER_DELAY_EXCHANGE_NAME);
                                messageLog.setRouter(MallRegisterConstants.ORDER_SECKILL_ROUTE_NAME);
                                PayloadDTO payload = new PayloadDTO();
                                payload.setEntity(JsonUtil.toJson(messageLog));
                                payload.setExchange(MallRegisterConstants.ORDER_DELAY_EXCHANGE_NAME);
                                payload.setRouter(MallRegisterConstants.ORDER_SECKILL_ROUTE_NAME);
                                messageFactory.get().send(payload);
                                return orderSn;
                            } catch (Exception e) {
                                log.error("创建秒杀订单发送消息失败：{}", e.getMessage());
                            }
                        }
                    }
                }
            }
        }

        return null;
    }
}
